using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
using Nuwanda.TfsSpotlight.Shell.Exceptions;
using Nuwanda.TfsSpotlight.Shell.Expressions;
using Nuwanda.TfsSpotlight.Shell.Proxies.Interfaces;
using Nuwanda.TfsSpotlight.Shell.Services.Interfaces;
using StructureMap;

namespace Nuwanda.TfsSpotlight.Shell.Services
{
    [Pluggable("ShellMenuService")]
    public class MenuService : IMenuService
    {
        private readonly IMenuState _menuState;
        private readonly IMenuStrip _menuStrip;
        private readonly IToolStrip _toolStrip;

        private Dictionary<string, ContextMenu> ContextMenus { get; set; }

        public MenuService(IMenuState menuState, IMenuStrip menuStrip, IToolStrip toolStrip)
        {
            this._menuState = menuState;
            this._menuStrip = menuStrip;
            this._toolStrip = toolStrip;

            this._menuStrip.Visible = false;

            this.ContextMenus = new Dictionary<string, ContextMenu>();
        }

        /// <summary>Allows to register given <paramref name="command"/></summary>
        /// <param name="command">Command to register</param>
        /// <returns>Expression that allows fluent configuration</returns>
        /// <exception cref="MenuConfigurationException">Thrown when the command has been already registered.</exception>
        public ICommandConfigurationExpression RegisterCommand(string command)
        {
            if(this._menuState.Contains(command))
            {
                throw new MenuConfigurationException(String.Format("The '{0}' command has been already registered.", command));
            }

            var commandConfiguration = new CommandConfiguration(this, command);
            this._menuState.Add(commandConfiguration);

            return new CommandConfigurationExpression(commandConfiguration);
        }

        /// <summary>Allows to register an activator for a given <paramref name="command"/></summary>
        /// <param name="command">Command to register activator for</param>
        /// <returns>Expression that allows fluent configuration</returns>
        /// <exception cref="MenuConfigurationException">Thrown when the command has not been registered yet.</exception>
        public IActivatorConfigurationExpression RegisterCommandActivator(string command)
        {
            if (this._menuState.Contains(command) == false)
            {
                throw new MenuConfigurationException(String.Format("The '{0}' command has not been registered yet.", command));
            }

            return new ActivatorConfigurationExpression(this._menuState[command]);
        }

        /// <summary>Allows to configure the tool strip</summary>
        /// <returns>Expression that allows fluent configuration</returns>
        public IToolStripConfigurationExpression ConfigureToolStrip()
        {
            return new ToolStripConfigurationExpression(this._menuState, this._toolStrip);
        }

        /// <summary>Allows to add custom elements to the tool strip</summary>
        /// <param name="command">Command to associate custom elements with</param>
        /// <returns>Expression that allows fluent configuration</returns>
        /// <exception cref="MenuConfigurationException">Thrown when the command has not been registered yet.</exception>
        public ICustomToolStripConfigurationExpression ConfigureCustomToolStrip(string command)
        {
            if (this._menuState.Contains(command) == false)
            {
                throw new MenuConfigurationException(String.Format("The '{0}' command has not been registered yet.", command));
            }

            return new CustomToolStripConfigurationExpression(this._menuState[command], this._toolStrip);
        }

        public IContextMenuConfiguretionExpression ConfigureContextMenu(string contextMenuName)
        {
            if(String.IsNullOrEmpty(contextMenuName))
                throw new MenuConfigurationException("The context menu name must be specified.");

            if(this.ContextMenus.ContainsKey(contextMenuName))
                throw new MenuConfigurationException(String.Format("The context menu named '{0}' has been already registered.", contextMenuName));

            var contextMenu = new ContextMenu(contextMenuName);
            this.ContextMenus.Add(contextMenuName, contextMenu);

            return new ContextMenuConfiguretionExpression(contextMenu);
        }

        public ContextMenuStrip GetContextMenu(string contextMenuName)
        {
            if(this.ContextMenus.ContainsKey(contextMenuName) == false)
                throw new MenuConfigurationException(String.Format("The '{0}' context menu has not been registered yet.", contextMenuName));

            return this._toolStrip.ToolStripInvoker.RunAndReturnInControlsThread(
                delegate 
                { 
                    var contextMenuStrip = new ContextMenuStrip();
                    contextMenuStrip.ShowCheckMargin = false;

                    foreach (var commandName in this.ContextMenus[contextMenuName].Commands)
                    {
                        var commandConfiguration = this._menuState[commandName];
                        var contextMenuItem = commandConfiguration.ToContextMenuItem();
                        contextMenuStrip.Items.Add(contextMenuItem);
                    }

                    return contextMenuStrip;
                });
        }

        /// <summary>Invalidates commands using activators associated with a given <paramref name="key"/></summary>
        /// <param name="key">Key to select acivators</param>
        public void InvalidateCommands(object key)
        {
            foreach (var command in this._menuState.RegisteredCommands)
            {
                command.Invalidate(key);
            }
        }

        /// <summary>Invalidates all commands using all activators</summary>
        public void InvalidateCommands()
        {
            foreach (var command in this._menuState.RegisteredCommands)
            {
                command.Invalidate();
            }
        }

        /// <summary>Disables commands with activators for a given <paramref name="key"/></summary>
        /// <param name="key">Key to select activators</param>
        public void DisableCommands(object key)
        {
            foreach (var command in this._menuState.RegisteredCommands)
            {
                command.Disable(key);
            }
        }

        /// <summary>Retrieves command by a <paramref name="shortcut"/></summary>
        /// <param name="shortcut">Shortcut to select</param>
        /// <returns>Command</returns>
        public string GetCommandNameByShortcut(Keys shortcut)
        {
            foreach (var command in this._menuState.RegisteredCommands)
            {
                if (command.Shortcut == shortcut)
                    return command.Command;
            }

            return String.Empty;
        }

        /// <summary>Runs given <paramref name="command"/></summary>
        /// <param name="command">Command to run</param>
        public void RunCommand(string command)
        {
            if (this._menuState.Contains(command) == false)
                throw new CommandNotRegisteredException(String.Format("The '{0}' command is not registered.", command));

            var commandConfiguration = this._menuState[command];
            var commandImpl = StructureMap.ObjectFactory.GetNamedInstance<ICommand>(command);

            if (commandImpl == null)
                throw new NoCommandImplementationException(String.Format("The implementation for '{0}' command was not found.", command));

            if(commandConfiguration.Asynchronous)
            {
                ThreadPool.QueueUserWorkItem(delegate { commandImpl.Execute(); });
            }
            else
            {
                commandImpl.Execute();
            }
        }
    }
}